package isjinhao.jlox.grammar.expression.visitor;

import isjinhao.jlox.grammar.expression.represention.*;
import isjinhao.jlox.lexical.TokenType;
import isjinhao.jlox.run.RuntimeError;
import isjinhao.jlox.lexical.Token;

/**
 * @Author ISJINHAO
 * @Date 2021/2/25 20:25
 */
public class ExpressionEvaluator implements ExpressionVisitor<Object> {

    @Override
    public Object visitArithmeticExpr(Arithmetic expr) {
        Object left = visit(expr.getLeft());
        Object right = visit(expr.getRight());
        Token operator = expr.getOperator();
        switch (operator.getType()) {
            case PLUS:
                checkNumberOperands(expr.getOperator(), left, right);
                if (left instanceof Double && right instanceof Double) {
                    return (double) left + (double) right;
                }
                if (left instanceof String && right instanceof String) {
                    return left + (String) right;
                }
                throw new RuntimeError(expr.getOperator(),
                        "Operands must be two numbers or two strings.");
            case MINUS:
                checkNumberOperands(expr.getOperator(), left, right);
                return (double) left - (double) right;
            case SLASH:
                checkNumberOperands(expr.getOperator(), left, right);
                return (double) left / (double) right;
            case STAR:
                checkNumberOperands(expr.getOperator(), left, right);
                return (double) left * (double) right;
            default:
                throw new RuntimeError(operator, "操作符类型不对，当前是：" + operator.getLexeme());
        }
    }

    private void checkNumberOperands(Token operator, Object left, Object right) {
        if (left instanceof Double && right instanceof Double) {
            return;
        }
        throw new RuntimeError(operator, "Operands must be numbers.");
    }

    @Override
    public Object visitComparisonExpr(Comparison expr) {
        Object left = visit(expr.getLeft());
        Object right = visit(expr.getRight());
        Token operator = expr.getOperator();
        switch (operator.getType()) {
            case BANG_EQUAL:
                return !isEqual(left, right);
            case EQUAL_EQUAL:
                return isEqual(left, right);
            case GREATER:
                checkNumberOperands(expr.getOperator(), left, right);
                return (double) left > (double) right;
            case GREATER_EQUAL:
                checkNumberOperands(expr.getOperator(), left, right);
                return (double) left >= (double) right;
            case LESS:
                checkNumberOperands(expr.getOperator(), left, right);
                return (double) left < (double) right;
            case LESS_EQUAL:
                checkNumberOperands(expr.getOperator(), left, right);
                return (double) left <= (double) right;
            default:
                throw new RuntimeError(operator, "操作符类型不对，当前是：" + operator.getLexeme());
        }
    }

    private boolean isEqual(Object a, Object b) {
        if (a == null && b == null) {
            return true;
        }
        if (a == null) {
            return false;
        }
        return a.equals(b);
    }

    @Override
    public Object visitGroupingExpr(Grouping expr) {
        return visit(expr.getExpression());
    }

    @Override
    public Object visitLiteralExpr(Literal expr) {
        return expr.getValue();
    }

    @Override
    public Object visitLogicExpr(Logic logic) {
        Token operator = logic.getToken();

        Object leftValue = visit(logic.getLeft());
        Object rightValue = visit(logic.getRight());

        if (operator.getType().equals(TokenType.OR)) {
            return (boolean) leftValue || (boolean) rightValue;
        }

        if (operator.getType().equals(TokenType.AND)) {
            return (boolean) leftValue && (boolean) rightValue;
        }

        throw new RuntimeError(operator, "操作符类型不对，当前是：" + operator.getLexeme());
    }

    @Override
    public Object visitUnaryExpr(Unary expr) {
        Object right = visit(expr.getRight());
        Token operator = expr.getOperator();
        switch (operator.getType()) {
            case BANG:
                return !isTruthy(right);
            case MINUS:
                checkNumberOperand(expr.getOperator(), right);
                return -(double) right;
            default:
                throw new RuntimeError(operator, "操作符类型不对，当前是：" + operator.getLexeme());
        }
    }

    @Override
    public Object visitVariableExpr(Identifier identifier) {
        throw new RuntimeError(identifier.getName(), "标识符为 nil");
    }

    private void checkNumberOperand(Token operator, Object operand) {
        if (operand instanceof Double) {
            return;
        }
        throw new RuntimeError(operator, "Operand must be a number.");
    }

    private boolean isTruthy(Object object) {
        if (object == null) {
            return false;
        }
        if (object instanceof Boolean) {
            return (boolean) object;
        }
        return true;
    }

}
